/**
 * 
 */
package org.statewalker.core.server;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * @author kotelnikov
 */
public abstract class AbstractAccessor {

    private Map<Class<?>, Object> fAccessors = new HashMap<Class<?>, Object>();

    /**
     * 
     */
    public AbstractAccessor() {
    }

    @SuppressWarnings("unchecked")
    public <T> T getAccessor(Class<T> cls) {
        T accessor = (T) fAccessors.get(cls);
        if (accessor == null) {
            accessor = newAccessor(cls);
            fAccessors.put(cls, accessor);
        }
        return accessor;
    }

    protected abstract Object getValue(String key);

    @SuppressWarnings("unchecked")
    public <T> T newAccessor(Class<T> cls) {
        InvocationHandler handler = new InvocationHandler() {
            private Object convertResult(Object result, Class<?> returnType) {
                // Return the default value for null
                if (result == null) {
                    if (returnType.isPrimitive()) {
                        if (returnType == boolean.class) {
                            return false;
                        } else if (returnType == byte.class) {
                            return 0;
                        } else if (returnType == char.class) {
                            return 0;
                        } else if (returnType == int.class) {
                            return 0;
                        } else if (returnType == long.class) {
                            return 0;
                        } else if (returnType == float.class) {
                            return 0;
                        } else if (returnType == double.class) {
                            return 0;
                        }
                    }
                }
                return result;
            }

            public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
                String name = method.getName();
                Class<?>[] paramTypes = method.getParameterTypes();
                if (name.startsWith("get") && paramTypes.length == 0) {
                    String key = normalizeKey(name.substring("get".length()));
                    Object result = getValue(key);
                    return convertResult(result, method.getReturnType());
                } else if (name.startsWith("set")
                    && paramTypes.length == 1
                    && args.length == 1) {
                    String key = normalizeKey(name.substring("set".length()));
                    Object value = args[0];
                    setValue(key, value);
                    return null;
                } else if ("toString".equals(name)) {
                    return proxy.toString();
                } else if ("hashCode".equals(name)) {
                    return proxy.hashCode();
                } else if ("equals".equals(name)) {
                    return proxy.equals(args[0]);
                } else {
                    Method m = proxy.getClass().getMethod(name, paramTypes);
                    return m.invoke(proxy, args);
                }
            }

            private String normalizeKey(String name) {
                if (name.length() == 0) {
                    return "";
                }
                name = Character.toLowerCase(name.charAt(0))
                    + name.substring(1);
                return name;
            }
        };
        T impl = (T) Proxy.newProxyInstance(
            cls.getClassLoader(),
            new Class<?>[] { cls },
            handler);
        return impl;
    }

    protected abstract void setValue(String key, Object value);

}
